package com.wmyg.antifake.varaes.sbox;

import com.wmyg.antifake.utils.TsStringCreator;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * @author 王卓逸
 * @program TODO
 * @description TODO
 * @create 2019-05-09
 */
public class SboxCreator {

    public static void main(String[] args) {
//        String context = "000000000000000";
        String context = TsStringCreator.randomString(15);
        System.out.println(context);
        int[] secret = {2,1,5,6,3,9,4,3,2};
        String mi = encrypt(context, secret);
        System.out.println(mi);
        String goback = decrypt(mi, secret);
        System.out.println(goback);
    }

    public final static Spoint[][] S_BOX = {
            {new Spoint(1,5),new Spoint(3,1),new Spoint(5,0),new Spoint(4,3),new Spoint(2,5),new Spoint(1,1)},
            {new Spoint(3,4),new Spoint(5,4),new Spoint(5,1),new Spoint(4,2),new Spoint(0,3),new Spoint(0,5)},
            {new Spoint(0,2),new Spoint(5,2),new Spoint(4,0),new Spoint(2,1),new Spoint(5,3),new Spoint(1,3)},
            {new Spoint(1,0),new Spoint(2,4),new Spoint(4,5),new Spoint(5,5),new Spoint(3,3),new Spoint(0,1)},
            {new Spoint(3,2),new Spoint(0,0),new Spoint(3,0),new Spoint(4,4),new Spoint(4,1),new Spoint(2,0)},
            {new Spoint(2,2),new Spoint(3,5),new Spoint(1,2),new Spoint(1,4),new Spoint(0,4),new Spoint(2,3)},
    };

    public final static Spoint[][] S_BOX_1 = {
            {new Spoint(4,1),new Spoint(3,5),new Spoint(2,0),new Spoint(1,4),new Spoint(5,4),new Spoint(1,5)},
            {new Spoint(3,0),new Spoint(0,5),new Spoint(5,2),new Spoint(2,5),new Spoint(5,3),new Spoint(0,0)},
            {new Spoint(4,5),new Spoint(2,3),new Spoint(5,0),new Spoint(5,5),new Spoint(3,1),new Spoint(0,4)},
            {new Spoint(4,2),new Spoint(0,1),new Spoint(4,0),new Spoint(3,4),new Spoint(1,0),new Spoint(5,1)},
            {new Spoint(2,2),new Spoint(4,4),new Spoint(1,3),new Spoint(0,3),new Spoint(4,3),new Spoint(3,2)},
            {new Spoint(0,2),new Spoint(1,2),new Spoint(2,1),new Spoint(2,4),new Spoint(1,1),new Spoint(3,3)},
    };

    public final static String CODE_SOURCE = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    public static Spoint[][] codeString(String s) {
        Spoint[][] result = new Spoint[3][5];
        for (int i = 0;i<15;i++) {
            char c = s.charAt(i);
            int x = i/5;
            int y = i%5;
            int code = 0;
            if (c <= '9' && c >= '0') {
                code = c - '0';
            } else if (c <= 'Z' && c >= 'A') {
                code = c - 'A' + 10;
            }
            result[x][y] = new Spoint(code/6, code%6);
        }
        return result;
    }

    public static String parseCode(Spoint[][] s) {
        StringBuffer result = new StringBuffer();
        for (Spoint[] ss :
                s) {
            for (Spoint sss :
                    ss) {
                result.append(CODE_SOURCE.charAt(sss.getX()*6 + sss.getY()));
            }
        }
        return result.toString();
    }

    //S盒转换
    public static Spoint[][] charChange(Spoint[][] in, Spoint[][] sbox) {
        Spoint[][] result = new Spoint[3][5];
        for (int i = 0; i < 3; i++ ) {
            for (int j = 0; j < 5; j++ ) {
                result[i][j] = sbox[in[i][j].getX()][in[i][j].getY()];
            }
        }
        return result;
    }

    //分解转换
    public static Spoint[][] phaseChange(Spoint[][] in, int direction) {
        Spoint[][] result = new Spoint[3][5];
        for (int i = 0; i < 3; i++ ) {
            for (int j = 0; j < 5; j++ ) {
                int x = 0;
                int y = 0;
                if (direction == 0) {
                    if (j == 4) {
                        y = 0;
                        if (i == 2) {
                            x = 0;
                        } else {
                            x = i + 1;
                        }
                    } else {
                        y = j + 1;
                        x = i;
                    }
                    result[i][j] = new Spoint(in[i][j].getY(), in[x][y].getX());
                } else {
                    if (j == 0) {
                        y = 4;
                        if (i == 0) {
                            x = 2;
                        } else {
                            x = i - 1;
                        }
                    } else {
                        y = j - 1;
                        x = i;
                    }
                    result[i][j] = new Spoint(in[x][y].getY(), in[i][j].getX());
                }
            }
        }
        return result;
    }

    //行换
    public static Spoint[][] rowChange(Spoint[][] in, int direction) {
        Spoint[][] result = new Spoint[3][5];
        for (int i = 0; i < 3; i++ ) {
            for (int j = 0; j < 5; j++ ) {
                if (direction == 0) {
                    result[i][j] = in[i][(j + i + 1)%5];
                } else {
                    result[i][j] = in[i][(j + 5 - i - 1)%5];
                }
            }
        }
        return result;
    }

    //列换
    public static Spoint[][] columnChange(Spoint[][] in, int direction) {
        Spoint[][] result = new Spoint[3][5];
        for (int i = 0; i < 3; i++ ) {
            for (int j = 0; j < 5; j++ ) {
                if (direction == 0) {
                    result[i][j] = in[(i + j + 1)%3][j];
                } else {
                    result[i][j] = in[(i + 6 - j - 1)%3][j];
                }
            }
        }
        return result;
    }

    //翻转
    public static Spoint[][] turnSide(Spoint[][] in) {
        Spoint[][] result = new Spoint[3][5];
        for (int i = 0; i < 3; i++ ) {
            for (int j = 0; j < 5; j++ ) {
                result[i][j] = new Spoint(5 - in[i][j].getX(), 5 - in[i][j].getY());
            }
        }
        return result;
    }

    public static String encrypt(String in, int[] secret) {
        Spoint[][] start = codeString(in);
        for (int i=0;i<secret.length;i++) {
            start = charChange(start, S_BOX);
            start = rowChange(start, 0);
            start = columnChange(start, 0);
            start = phaseChange(start, 0);
            start = turnSide(start);
            switch (secret[i]%5) {
                case 0:
                    start = charChange(start, S_BOX);
                    break;
                case 1:
                    start = rowChange(start, 0);
                    break;
                case 2:
                    start = columnChange(start, 0);
                    break;
                case 3:
                    start = phaseChange(start, 0);
                    break;
                default:
            }
        }
        return parseCode(start);
    }

    public static String decrypt(String in, int[] secret) {
        Spoint[][] start = codeString(in);
        for (int i=0;i<secret.length;i++) {
            switch (secret[secret.length-1-i]%5) {
                case 0:
                    start = charChange(start, S_BOX_1);
                    break;
                case 1:
                    start = rowChange(start, 1);
                    break;
                case 2:
                    start = columnChange(start, 1);
                    break;
                case 3:
                    start = phaseChange(start, 1);
                    break;
                default:
            }
            start = turnSide(start);
            start = phaseChange(start, 1);
            start = columnChange(start, 1);
            start = rowChange(start, 1);
            start = charChange(start, S_BOX_1);
        }
        return parseCode(start);
    }

}
